[contents] [prev] [top] (5 out of 5)

What is Object-Oriented Programming?

You can look at object-oriented programming as a conceptually simple extension of what good programmers have always done or as a radically new approach to software creation. In truth, object-oriented programming is both. As such, you can approach learning it from either point of view. The rest of this chapter explores object-oriented programming as an extension to conventional, procedural programming.

Procedural Programming

Procedural programming is based on two entities: data structures and functions. Data structures contain information. Functions act on that information. Traditional programming tools typically create and maintain these two entities separately.

Smart programmers have always found ways to reuse data structures and functions. Reusing them is a good idea-they are already tested, they behave in understood ways, and they often represent data or actions that many programs need.

However, once you move a data structure from one program to another, you discover that you need some functions to manage it. Or, when you move a function, you discover the need for a data structure for it to manipulate. While traditional programming languages don't provide much support for this sort of reuse, good programmers have still done it.

Combining Structure and Function

Object-oriented programming combines data structures and the functions that act on them into a single unit. This combination of a data structure plus the functions that act on that data structure is called an object.

You can change the contents of the object's data structure using the functions that come with the object. These functions don't need a separate data structure. You can think of the object as a component.

Combining the two basic entities-data structures and functions-into objects makes it easier to move them to other programs and reuse them. Data structures and functions that represent the information and tasks of a particular enterprise can be saved and shared, making programming tasks quicker to complete and easier to maintain.

Protecting Data

Objects provide the advantage of data protection. Many programming problems are the result of changing a data structure in a way that the programmer didn't expect or didn't intend.

An object controls access to its data by making data inaccessible from outside-you can only get and set its values by using functions for getting and setting that are provided by the object. The correct object-oriented programming term is encapsulation. Encapsulation allows an object to provide quality control for its own data.

Thinking in Types

You can think of each type of object as representing a template. This template is similar to the defined types that many other languages support, like a record or a structure. As a template, the object can be used to create new objects that share similar features. Each object created from the template gets a new, initialized copy of the data structure defined for that type of object. Each object created from the template also gets access to the functions that act on its data structure. You can think of the template as a factory for building objects of a particular type.

In object-oriented programming, such a template is called a class. Every object is built from instructions that are associated with its class. Objects of the same type are instances of the same class. Every object is an instance of any classes that it belongs to or inherits from.

Properties and Methods

Suppose you want to represent dogs in a computer program. You start by listing things you want to keep track of about a dog. In procedural programming the things you want to keep track of are stored in your data structure. Your data structure might contain items or fields such as name, owner, breed, and temperament. Each object will store data for one dog instance (See Figure 1-1).

Figure 1-1: Storing data for dog instances

In object-oriented programming, these items or fields are sometimes called properties. Most properties are associated with a particular dog, that is, with a given instance of the class Dog. Properties that are associated with a particular dog are called instance variables. You might also want to define properties for your program that are associated with all dogs-for example, you might want to keep track of the population of dogs. Properties that are associated with the Dog class in general are called class variables.

Now that you have a data structure for a dog, the next step in designing a program is to figure out what a dog does. A dog performs certain activities, which most programmers would refer to as procedures or functions. In your dog program, these functions might include eat, sleep, run, fetch, bark, bite, and sniff. In object-oriented programming, the actions that an object carries out are sometimes called behaviors.

If you wrote a procedural program, most of your program would consist of functions. In ScriptX, functions that are associated with a particular object or class of objects are called methods, in part to differentiate them from functions that are not associated with any object. To summarize, functions that are associated with objects are called methods; those that are not associated with objects are called simply functions.

Creating Objects and Classes

Although the formal discussion of the syntax for defining objects and classes doesn't occur until Chapter 6, ScriptX is a very natural and readable language. This is a good place to jump in and examine ScriptX code.

This code sample creates a class called Dog and an instance of Dog called nikki. Enter the first eight lines of code below, beginning with the word class. This creates the Dog class. The Dog class can then be used as a template for creating Dog objects. Each Dog object will have all the properties, such as owner and breed, that are defined in the template. The Dog class also defines three instance methods-bark, fetch, and sniff-just enough to give you the general idea.

--
-- this code example, and others throughout this volume, are on
-- the CD-ROM with the ScriptX Language and Class Library 
-- 
class Dog()		
	instance variables 
		name, owner, breed, age, length, weight, sex, temperament
	instance methods
		method bark self -> print "makes a lot of noise" 
		method fetch self -> print "fetches a stick" 
		method sniff self -> print "sticks nose into things" 
end
Dog

The return line ( Dog ) indicates that the ScriptX bytecode compiler has compiled the Dog class. That means you can now create Dog objects.

The next four lines of code create the first Dog object, which is assigned to the variable nikki. In this example, the value of four of the instance variables for nikki are set at initialization. Since the other four are ignored, their values will be undefined until they are explicitly set.



object nikki (Dog)
	settings name:"Nikki", owner:#("Jocelyn","Ken"), sex:@female,
		breed:"English Springer Spaniel", temperament:@nervous
end
Dog@0xef8908

Instance variables are like slots or buckets. You can put any ScriptX object into the "slots" that are defined for the dog. This example uses several of the most common classes of object in the system. The dog's name instance variable contains a StringConstant object "Nikki". The dog's owner instance variable contains an Array object. Each item in that array is also a StringConstant object. The sex and temperament instance variables contain NameClass objects, also known as name literals.

The return value ( Dog@0xef8908 ) tells you the Dog object exists, and it gives the memory address. Of course, this memory address will differ with each computing session, and on each platform. (Unlike the real Nikki, this dog stays put at 0xef8908 until you no longer want her around!)

Ignoring for now the ScriptX language syntax, notice how easy it is get back information about nikki. Here's what you type into the Listener to get access to the owner instance variables defined by the object nikki.

nikki.owner
#("Jocelyn", "Ken")

Now consider the three instance methods defined for the class Dog: bark, fetch, and sniff. In object-oriented programming you can think of calling a method as sending a message to an object, telling that object to perform some operation.



fetch nikki 
"fetches a stick"
OK

The third line ( OK ) is the return value of the fetch method. The print function in the definition of the fetch method prints "fetches a stick" to the Listener window. This string is not itself a return value-printing a string is just an operation that fetch carries out. Throughout this manual, an arrow ( ) indicates output from ScriptX to your Listener or debugger window, including both printed messages and return values. Output from ScriptX is also indented, to indicate that you do not type it in.

This example shows how you can create a description of a dog's properties and behavior as part of a dog template. This template is the Dog class, from which usable Dog objects can be created. Each Dog from this template has the same properties and the same general behavior, as described by its methods.

Note that each dog could behave differently, even though all Dog objects share the same behavior. This is because a dog's methods have access to the object's own data. You could write a fetch method such that a Dog object with good temperament fetches faster than a Dog object with bad temperament.

Beyond Traditional Programming

So far, object-oriented programming simply represents a codified system for doing what good programmers have always done: protect their data and reuse trusted, tested code. This section focuses on things you can do with object-oriented programming that couldn't be simulated in procedural programming languages.

Defining by Difference

Many times, programmers need to create new code that is similar to existing code. In particular, there is often a need to modify the functionality of something that already works, without totally rewriting it. Object-oriented programming provides just this type of extended reuse, allowing you to create a new class using an existing class as a base. The new class is a specialization of the existing class. You only need to define how a new class is different from its parent class-what properties and behavior it adds to or modifies in the base class. In other respects, the new class is the same as the base from which it was created.

Object-oriented programming is characterized by inheritance. This means that a specialized class inherits the properties and behavior of its parent, which leads to a hierarchy of classes, grouped together based on similarities in their data structures and methods. This grouping of classes provides a family tree of classes that range from general to specialized.

Going back to the dog example, say you want a world that is populated by many kinds of dogs. The example created one class, Dog. For some programs, that might be adequate. But what if you want to create a program that really models all the different ways that dogs behave?

Object-oriented programmers often design a hierarchy of classes that reflect the specialization of behavior and properties they need to model. The Dog class has general properties and behavior for all dogs. Using inheritance and specialization, you can specialize that Dog class to create more specialized classes that have particular characteristics and behavior. For example, you could divide dogs into various categories like hunting dogs and lap dogs as shown in Figure 1-2. There is no right way to categorize dogs; how you set up the dog hierarchy should depend on how you want to use them. Think of all the ways to divide up the world of dogs!

Figure 1-2: Creating inheritance hierarchies of dogs

One of the advantages to creating a hierarchy of classes is that different classes can share the same behavior, or methods. This sharing of behavior is called polymorphism. Each class or object that inherits a method can define its own version of that method.

Technically, when your ScriptX program calls a method, it does not call the method directly. For each method name, ScriptX creates a generic function. Unlike regular functions, generic functions are always associated with an object. Generic functions look just like regular functions, except that the first argument is always an object. You always call a generic function on a particular object. ScriptX determines which method to call, based on the value of the first argument to the function, which is the object you are calling the function on. Methods are really versions of generic functions, defined for a particular class or object.

Generic functions reduce the complexity of the system of objects. A programmer can get work done using the small vocabulary of generic functions that is shared by a family of objects. Rather than a single function with a single behavior, a generic function stands for a whole range of general behavior, shared by a whole set of objects. Each class of object supplies an appropriate implementation of the generic function . A programmer can easily learn to use all dog classes, since every dog implements bark, fetch, and sniff-the advantage is that each dog can implement them in its own way. For example, each specification of the Dog class can have its own implementation of bark. The following example shows how to specialize the Dog class by creating two new subclasses: HuntingHound and LapDog. In object-oriented programming terminology, Dog is a superclass of HuntingHound and LapDog. HuntingHound and LapDog are subclasses of Dog. Notice the specialization of the bark generic function on the HuntingHound and LapDog classes.

class HuntingHound (Dog)
	instance methods
		method bark self -> print "wooof, wooof"
end
HuntingHound
class LapDog (Dog)
	instance methods
		method bark self -> print "yip, yip, yip, yip, yip"
end
LapDog

Hunting hounds and lap dogs continue to share the same implementation of the fetch and sniff methods, since they are not specialized. The bark method, although it was already defined by the Dog class, has been redefined. In object-oriented programming terminology, to specialize behavior in this manner is to override a method.

To override a method is not necessarily to replace it with a completely new version. A subclass can invoke the version of a method that is provided by a superclass in its own implementation of that method. Here is an example.

class Shihtzu (LapDog)
	instance methods
		method bark self -> (
			nextMethod self
			print "jumps up and down"
		)
end
Shihtzu
object tyler (Shihtzu) settings name:"Tyler" end
Shihtzu@0xefcf88
bark tyler
"yip, yip, yip, yip, yip"
"jumps up and down"
OK


Note - Since you are used to getting a return value in the Listener window, from now on, return values are shown only where required by the discussion.

In the example above, the expression nextMethod self causes any instances of Shihtzu to call the bark method defined by the next superclass that implements it. In this case, the next implementing superclass is LapDog. Each instance of Shihtzu first calls the more general bark method for all instances of LapDog, and then continues with the specialized version for a Shihtzu object.

Polymorphism, the sharing of behavior, allows you to be more general because you are specific in indicating which object should fulfill the request. The programmer uses the generic function to describe what should be done and leaves it to the object to determine how exactly to do it.

By comparison, functions in a procedural program have only a single version. You must create a separate function or add special case code and additional arguments for each new thing you want to do. Modifying a procedural program increases the complexity of the system, and makes it harder for you to maintain and scale your code.

Protocols

A class that defines a common set of methods that every subclass either inherits a definition of, or creates its own definition for, is said to define a protocol. For example, the Dog class defines a protocol in the bark, fetch, and sniff methods. These three methods are implemented for every instance of Dog, making them the Dog protocol. A protocol is a set of generic functions that is defined for every class in some branch of a class hierarchy.

In some object-oriented programming environments, the concept of protocol is much more formal. In ScriptX, it is an informal term that identifies a set of shared behaviors. As a programmer, it makes your life easy to know that every dog, whether a golden retriever or a cocker spaniel, knows how to bark. Each subcategory of dog, each individual dog breed, or even each individual dog, can define its own bark, but you are guaranteed that if you have a dog, it knows how to bark.

Defining New Behavior

You can create completely new behavior in a subclass. This example creates a BassetHound class that implements the drool method, a method that other dogs, with less active salivary glands and more active facial muscles, might not share. In the dog world example, drool behavior is peculiar to basset hounds.

class BassetHound (HuntingHound)
	instance methods
		method drool self -> print "slobbers all over everything"
end
-- now create an instance of BassetHound
object vaps (BassetHound)
	settings name:"Vaps", owner:"The Crosbys"
end
-- call the drool generic function on vaps
drool vaps
"slobbers all over everything"

Note the following distinction, which is important in object-oriented programming. In the previous example, it isn't the BassetHound class that drools. The BassetHound class is a template used to define the properties and behavior of basset hounds. To be more precise, the BassetHound class is a template that defines the ways in which basset hounds differ from hunting dogs, and from dogs in general. But you have to create an actual instance of BassetHound, a BassetHound object, in order to see a dog drool.

Multiple Inheritance

Up to this point, the model for the world of dogs contained only instances of some subclass of Dog. That might do if you were only interested in one aspect of a dog's behavior. But in the real world, systems are far more complex. Some unlucky dogs are wild or stray, and do not have owners. Instead, they have territories and live in packs. Other dogs are tame, and have both owners and veterinarians. Naturally, wild dogs behave very differently from pets. But so far, the dog world example is set up as if all dogs have a property called owner, an instance variable that stores the owner's name.

Fortunately, ScriptX allows any object or class to inherit from more than one parent class. This multiple inheritance allows you to factor the behavior of dogs into several different parent classes. To factor behavior is to divide or separate aspects of behavior in logical ways. The ability to factor information or behavior is one of the benefits of multiple inheritance. For example, you can keep the Dog class, renamed Canine, and create two new classes, Pet and WildAnimal, that contain aspects of canine life that are peculiar to domestic and feral dogs (see Figure 1-3). Mixing these classes together will produce dogs with the desired characteristics.

Figure 1-3: Multiple inheritance and dogs

The following example creates a PetDog class that uses multiple inheritance to define properties and behavior. The new class is a template for creating objects that share characteristics of all of its parents. PetDog inherits from both the Canine and Pet classes, which incorporate the general properties and behavior of the previously defined Dog class, and add new ones.


Note - If you have been using the ScriptX Language and Class Library in one continuous session since the beginning of this chapter, you will not be able to redefine the Dog class until you eliminate all instances of Dog from the system. Instead of reusing Dog, this example uses the name Canine as a root class for dogs.

class Canine ()
instance variables
age, length, weight, sex, temperament
instance methods
method bark self -> print "makes a lot of noise"
method sniff self -> print "sticks nose into things"
method sleep self -> print "lazy dog sleeps all day"
end
class Pet ()
instance variables
name, owner, breed, veterinarian, spayed
instance methods
method fetch self -> print "fetches a stick"
end
class PetDog (Pet, Canine)
end

The PetDog class does not actually define any new behavior or properties for PetDog objects. An instance of PetDog will have all the instance variables and instance methods that its superclasses define. Now create a PetDog object.

object tammy (PetDog)
settings name:"Tammy", owner:"the Metzenbergs", sex:@female,
spayed:@true, breed:"Siberian Husky", veterinarian:"Dr. Donovan"
end
fetch tammy
"fetches a stick"
sniff tammy
"sticks nose into things"

PetDog object tammy defines properties of both a Canine object and a Pet object. She barks, sniffs, and sleeps like a canine, and she fetches like a pet.

Although you would probably want to override the fetch method, you could easily create a Cat class and combine it with Pet to create the new class PetCat. In object-oriented terminology, Pet is being used to mix in characteristics of Pet with another class to create a new subclass that offers additional features.

As you work with ScriptX, you will find many examples of multiple inheritance in use. With multiple inheritance, it is easy to add new features to classes and objects you create. ScriptX ships with a library of media classes called the core classes. You can add functionality to your own classes by mixing them in with these predefined media classes. For example, if you wanted to have your PetDog object appear in a ScriptX window, you could create a new class that mixes PetDog with TwoDShape, one of the core classes. As an instance of TwoDPresenter, a TwoDShape object can present a graphic object, such as a bitmap. Mixing in the TwoDShape class would give your objects the ability to display a target object (such as a bitmap) in a window with a two-dimensional coordinate system.

Ease of Modification

Suppose you have already built a system that works. You have a trusted, tested sequence of logic. Do you want to add new functionality, when any change might break your entire system?

In an object-oriented environment, you can isolate changes so that they have an effect on only a particular class, a group of classes, or even a particular object. Suppose you have a pet dog that sleeps on top of his doghouse. You don't have to create a new class, or modify any existing classes. You can add this new behavior to a particular PetDog object when you create the object.

object snoopy (PetDog)
instance methods
method sleep self -> (
print "sleeps on top of his doghouse"
nextMethod self
)
settings name:"Snoopy", owner:"Charlie Brown", breed:"Beagle"
end
sleep snoopy
"sleeps on top of his doghouse"
"lazy dog sleeps all day"
OK
As this example shows, specialization in ScriptX is not limited to defining new classes. You can add new instance variables and define or override instance methods at any level, even for a single object.

Connections Between Objects

In ScriptX, as in all object-oriented languages, you often build complex data structures by connecting objects. ScriptX stores values by reference. When you assign or set an instance variable, you store a reference to another object. In this respect, instance variables create a network of connections between objects. These object connections allow you to connect information in ways that are natural and logical, avoiding duplication of information and functionality.

Suppose you want to know where pet dogs live. As defined in the previous section, the PetDog class inherits from both Pet and Canine. From the Pet class, a pet dog inherits the instance variable owner. If an owner has an address instance variable, you can get access to that information through the dog. The following example defines the Owner class and creates an instance of Owner and an instance of PetDog.

class Owner ()
instance variables
name, address
end
-- create an owner
object janne (Owner)
settings name:"Janne", address:"Fairfax, California, USA"
end
-- create a dog for the owner to own
object odan (PetDog)
settings breed:"Mastiff", sex:@male, name:"Odan"
end

If the dog's owner instance variable is set, you can figure out where the dog lives by examining the owner's address instance variable.

odan.owner := janne
odan.owner.address
"Fairfax, California, USA"

This connection makes additional information available about a pet dog, without modifying the Canine or PetDog classes. (In an actual program, you could make this connection at initialization, and introduce error and type checking.)

Building and Maintaining Libraries

Traditional libraries of code are collections of pretested functions that extend a language. These functions typically address narrow areas of specific behavior, such as database design or special mathematics. Rarely are all the necessary data structures provided. Most libraries can't attempt to solve complete, application-level problems. The library code is intended to be connective. As the programmer, you write the majority of the code in an application. You construct the problem-solving framework and use library code where appropriate to fill in the gaps.

Object-oriented libraries are collections of classes arranged in hierarchies that resemble family trees. The library classes and the objects created from those classes are complete and ready to use. They can address larger scale problems than function libraries can. The programmer can use them to accelerate development, making it possible to prototype rapidly by using library objects as a base for exploration.

More importantly, it is possible for an object-oriented system to provide a set of objects that have built-in relationships. By creating collections of cooperating objects, it's possible for an object-oriented system's libraries to act as pretested problem-solving frameworks. The framework provides the majority of the code. You plug your objects into the slots left in the framework.

It's easy to modify object-oriented systems without breaking them, because the changes are localized in individual objects, not spread out across many data structures and functions. Adding new behavior to an object-oriented system can often be as simple as creating a new type of object and placing an object of that type in an existing system of code. The system is modified by changing the type of the object that receives a method. The trusted, tested sequence of logic doesn't have to change.

Special Kinds of Classes

The ScriptX core classes library defines a few classes that cannot be specialized. This means you cannot create new subclasses of those classes. You cannot add methods or instance variables to those classes, or to instances of those classes. These classes are said to be sealed. Sealed classes are often internally optimized and as such do not provide behavior that would be available to subclasses. The sealed classes include classes that represent numbers, Boolean values, names, gates, and threads. Most of the classes in the core classes are not sealed.

Another special kind of class, common throughout the core classes, is an abstract class. When a class hierarchy is organized, information is factored into superclasses so that common subclasses can share and reuse it. Often, when information is factored in this way, classes can result that provide partial information for their subclasses, but do not contain enough information to create fully-featured instances of themselves. These classes are called abstract classes. An abstract class can be subclassed or mixed in with another class, but it cannot be instantiated. Abstract classes exist as a basis for defining other classes. ScriptX prevents you from instantiating an abstract class. Any class that is not abstract is concrete.

A final distinction is between scripted classes and the core classes themselves. A scripted class is one that is created using the ScriptX language. (The core classes are created with Objects-In-C, an extension of the C programming language developed by Kaleida Labs, Inc.) All scripted classes are unsealed. Although there are some internal differences between scripted classes and the core classes, their external interface is the same.

Wrapping Up Objects

In summary, objects are data structures that are associated with both data and the functions, which are called methods, that act on that data. Objects are created from classes, which act as templates for building objects. Each object is an instance of its parent class.

Classes are organized in a hierarchy that resembles a family tree, based on the characteristics they share. Classes inherit the properties and behavior of their parent classes. When it is created, or instantiated, each object gets a copy of the instance variables and access to the instance methods that are defined for its class.

With each new class of objects, a programmer is free to define new instance variables and methods, or to override existing methods to behave in a new, appropriate way for the particular class. This redefinition can even incorporate the parent's version of the same method.

This class inheritance tree ranges from very general types at its root to more specialized types at the branches. The types in a particular branch of the tree share a common way of doing things. New classes can be added at any point in the tree. Almost any existing type can be the parent for a new type.

Much of the real power of an object-oriented environment is found in the class libraries that can be included with an object-oriented language. They allow a programmer to concentrate on the problem at hand, building solutions from reliable, tested components.

The primary benefits of object-oriented programming are reduced complexity, increased reusability, and extensibility. Objects are a way of bundling data and functions together to make a reusable software component. Objects protect their internal workings, hiding their data and generalizing their functions so that there are fewer details for a programmer to deal with. Classes are factories for making usable objects. Classes provide the basis for making new types simply by describing how the new type is different from an existing type.

Object-oriented programming gives you the ability to create a general framework which, because of polymorphism, continues to work with new and unexpected specializations of the system. All the basic frameworks in ScriptX, such as the animation compositor, depend on polymorphism. Because of polymorphism, you can rely on existing frameworks to work with new objects that you define in your title.


This document is part of the ScriptX Language Guide, one of the volumes of the ScriptX Technical Reference Series. ScriptX is developed by the ScriptX Engineering Team at Apple Computer, successor to the Kaleida Engineering Team at Kaleida Labs, Inc.

Copyright 1996 Apple Computer, Inc. All Rights Reserved.